﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Text;
using System.Xml;
using Microsoft.SharePoint;
using Navigant.Infrastructure.Cases;
using Navigant.Infrastructure.Helpers;
using Navigant.Model.Custodians;
using Navigant.Model.PreservationNotices;

namespace Navigant.Infrastructure.Repositories
{
    public class SharePointCustodianRepository : SharePointRepositoryBase<Custodian>, ICustodianRepository
    {
        #region Private Fields

        #endregion

        #region Public Constructors

        public SharePointCustodianRepository(ICase currentCase)
            : this(currentCase, null)
        {
        }

        public SharePointCustodianRepository(ICase currentCase, IUnitOfWork unitOfWork)
            : base(currentCase, unitOfWork)
        {
        }

        #endregion

        #region Protected Methods

        protected override string GetEntityName()
        {
            return "Custodians";
        }

        protected override int PersistNewItem(Custodian item)
        {
            using (SPSite site = new SPSite(item.ParentCase.ClientPortalId))
            {
                using (SPWeb web = site.OpenWeb(item.ParentCase.Id))
                {
                    web.AllowUnsafeUpdates = true;
                    SPList list = web.Lists[this.EntityName];
                    SPListItem listItem = list.Items.Add();
                    this.UpdateCustodianListItem(listItem, item);
                    return listItem.ID;
                }
            }
        }

        protected override void PersistUpdatedItem(Custodian item)
        {
            SPWeb web = null;
            SPSite site = null;
            try
            {
                if (this.RunWithElevatedPrivileges)
                {
                    SPSecurity.RunWithElevatedPrivileges(delegate()
                    {
                        site = new SPSite(item.ParentCase.ClientPortalId);
                        web = site.OpenWeb(item.ParentCase.Id);
                    });
                }
                else
                {
                    site = new SPSite(item.ParentCase.ClientPortalId);
                    web = site.OpenWeb(item.ParentCase.Id);
                }
                web.AllowUnsafeUpdates = true;
                SPList list = web.Lists[this.EntityName];
                SPListItem listItem = list.GetItemById(item.Id);
                this.UpdateCustodianListItem(listItem, item);
            }
            finally
            {
                if (site != null)
                {
                    site.Dispose();
                }
                if (web != null)
                {
                    web.Dispose();
                }
            }
        }

        #endregion

        #region Private Methods

        private void UpdateCustodianListItem(SPListItem listItem, Custodian item)
        {
            listItem.Web.AllowUnsafeUpdates = true;
            listItem[CustodianFactory.FieldNames.Name] = item.Name.FullName;
            try
            {
                listItem[CustodianFactory.FieldNames.CustodianName] = item.Name.FullName;
            }
            catch { }
            listItem[CustodianFactory.FieldNames.Email] = item.Email;
            listItem[CustodianFactory.FieldNames.IsInternal] = item.IsInternal;
            if (this.UseIsPersonFieldName(listItem.ParentList))
            {
                listItem[CustodianFactory.FieldNames.IsPerson] = item.IsPerson;
            }
            else
            {
                listItem[CustodianFactory.FieldNames.IsPeople] = item.IsPerson;
            }

            // Extended properties
            SPField field = null;
            foreach(string propertyName in item.UpdatedProperties)
            {
                field = null;
                try
                {
                    field = listItem.ParentList.Fields.GetFieldByInternalName(propertyName);
                }
                catch 
                {
                    try
                    {
                        field = listItem.ParentList.Fields.GetField(propertyName);
                    }
                    catch { }
                }
                if (field != null && 
                    !field.ReadOnlyField && 
                    field.InternalName != "Attachments")
                {
                    if (field.Type == SPFieldType.DateTime &&
                        string.IsNullOrEmpty(item.ExtendedProperties[propertyName]))
                    {
                        listItem[propertyName] = null;
                    }
                    else
                    {
                        listItem[propertyName] = item.ExtendedProperties[propertyName];
                    }
                }
            }
            listItem.Update();
        }

        private bool UseIsPersonFieldName(SPList custodianList)
        {
            foreach (SPField field in custodianList.Fields)
            {
                if (field.InternalName.ToLower() == CustodianFactory.FieldNames.IsPerson.ToLower())
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Build the where portion of the CAML query.
        /// </summary>
        /// <returns></returns>
        private string BuildCamlWhereClause(string extraCriteriaFieldName,
            string extraCriteriaFieldValue, PreservationNoticeCriteria noticeCriteria)
        {
            string whereClause = string.Empty;
            SPField field = this.GetField(extraCriteriaFieldName);

            // Build the correct CAML where clause if 
            // the extra criteria field name and value are specified
            if (field != null && 
                !string.IsNullOrEmpty(extraCriteriaFieldValue))
            {
                // Spaces must be escaped with "_x0020_" and hyphens removed
                StringBuilder sb = new StringBuilder();
                sb.Append("<Where><Eq><FieldRef Name='");
                sb.Append(field.InternalName);
                sb.Append("' /><Value Type='Text'>");
                sb.Append(extraCriteriaFieldValue);
                sb.Append("</Value></Eq></Where>");
                whereClause = sb.ToString();
            }
            else if (noticeCriteria != PreservationNoticeCriteria.Undefined &&
                noticeCriteria != PreservationNoticeCriteria.All)
            {
                string camlSearchString = this.GetCamlSearchStringRepresentation(noticeCriteria);
                if (!string.IsNullOrEmpty(camlSearchString))
                {
                    whereClause = string.Format("<Where>{0}</Where>", camlSearchString);
                }
            }
            Debug.WriteLine(whereClause);
            return whereClause;
        }

        /// <summary>
        /// Build the where portion of the CAML query.
        /// </summary>
        /// <returns></returns>
        private string BuildCamlWhereClauseSearch(string extraCriteriaFieldName,
            string extraCriteriaFieldValue, PreservationNoticeCriteria noticeCriteria)
        {
            string whereClause = string.Empty;
            SPField field = this.GetField(extraCriteriaFieldName);

            // Build the correct CAML where clause if 
            // the extra criteria field name and value are specified
            if (field != null &&
                !string.IsNullOrEmpty(extraCriteriaFieldValue))
            {
                StringBuilder sb = new StringBuilder();
                sb.Append("<Where><BeginsWith><FieldRef Name='");
                sb.Append(field.InternalName);
                sb.Append("'/><Value Type='Text'>");
                sb.Append(extraCriteriaFieldValue);
                sb.Append("</Value></BeginsWith></Where>");
                sb.Append("<OrderBy>");
                sb.Append("<FieldRef Name='Title'/>");
                sb.Append("</OrderBy>");
                whereClause = sb.ToString();
            }
            else if (noticeCriteria != PreservationNoticeCriteria.Undefined &&
                noticeCriteria != PreservationNoticeCriteria.All)
            {
                whereClause = string.Format("<Where>{0}</Where>",
                    this.GetCamlSearchStringRepresentation(noticeCriteria));
            }
            Debug.WriteLine(whereClause);
            return whereClause;
        }
        /// <summary>
        /// Returns the search CAML search expression for the given noticeCriteria
        /// </summary>
        /// <param name="noticeCriteria"></param>
        /// <returns></returns>
        private string GetCamlSearchStringRepresentation(PreservationNoticeCriteria noticeCriteria)
        {
            string searchStringRepresentation = string.Empty;

            switch (noticeCriteria)
            {
                case PreservationNoticeCriteria.All:
                    searchStringRepresentation = string.Empty;
                    break;
                case PreservationNoticeCriteria.Unacknowledged:
                    searchStringRepresentation = string.Format("<Or><Eq><FieldRef Name='Pres_x0020_Status'/><Value Type='Text'>{0}</Value></Eq><Eq><FieldRef Name='Pres_x0020_Status'/><Value Type='Text'>{1}</Value></Eq></Or>",
                        PreservationNoticeStatus.NoticeQueued.ToString(), PreservationNoticeStatus.Sent.ToString());
                    break;
                case PreservationNoticeCriteria.UnacknowledgedExternal:
                    searchStringRepresentation = string.Format("<And><Or><Eq><FieldRef Name='Pres_x0020_Status'/><Value Type='Text'>{0}</Value></Eq><Eq><FieldRef Name='Pres_x0020_Status'/><Value Type='Text'>{1}</Value></Eq></Or><Eq><FieldRef Name='Is_x0020_Internal'/><Value Type='Boolean'>0</Value></Eq></And>", 
                        PreservationNoticeStatus.NoticeQueued.ToString(), PreservationNoticeStatus.Sent.ToString());
                    break;
                case PreservationNoticeCriteria.UnacknowledgedInternal:
                    searchStringRepresentation = string.Format("<And><Or><Eq><FieldRef Name='Pres_x0020_Status'/><Value Type='Text'>{0}</Value></Eq><Eq><FieldRef Name='Pres_x0020_Status'/><Value Type='Text'>{1}</Value></Eq></Or><Eq><FieldRef Name='Is_x0020_Internal'/><Value Type='Boolean'>1</Value></Eq></And>",
                        PreservationNoticeStatus.NoticeQueued.ToString(), PreservationNoticeStatus.Sent.ToString());
                    break;
                case PreservationNoticeCriteria.Undefined:
                    throw new ArgumentException("Must pass in a valid Preservation Notice Criteria.", "noticeCriteria");
                case PreservationNoticeCriteria.Unsent:
                    searchStringRepresentation = "<Or><IsNull><FieldRef Name='Pres_x0020_Status' /></IsNull><Eq><FieldRef Name='Pres_x0020_Status' /><Value Type='Text'></Value></Eq></Or>";
                    break;
                case PreservationNoticeCriteria.UnsentExternal:
                    searchStringRepresentation = "<And><IsNull><FieldRef Name='Pres_x0020_Status' /></IsNull><Eq><FieldRef Name='Is_x0020_Internal'/><Value Type='Boolean'>0</Value></Eq></And>";
                    break;
                case PreservationNoticeCriteria.UnsentInternal:
                    searchStringRepresentation = "<And><IsNull><FieldRef Name='Pres_x0020_Status' /></IsNull><Eq><FieldRef Name='Is_x0020_Internal'/><Value Type='Boolean'>1</Value></Eq></And>";
                    break;
            }
            return searchStringRepresentation;
        }

        private XmlDocument GetCustodiansSchema(string extraCriteriaFieldName)
        {
            XmlDocument schema = new XmlDocument();
            SPField field = null;
            using (SPSite site = new SPSite(this.CurrentCase.ClientPortalId))
            {
                using (SPWeb web = site.OpenWeb(this.CurrentCase.Id))
                {
                    SPList list = web.Lists[this.EntityName];
                    try
                    {
                        field = list.Fields.GetField(extraCriteriaFieldName);
                    }
                    catch { }
                    if (field != null)
                    {
                        schema.LoadXml(field.SchemaXml);
                    }
                }
            }
            return schema;
        }

        #endregion

        #region ICustodianRepository Members

        public IList<Custodian> FindByCriteria(PreservationNoticeCriteria criteria)
        {
            return this.BuildEntitiesFromCaml(this.BuildCamlWhereClause(null,
                null, criteria));
        }

        public IList<Custodian> FindByExtraCriteria(string extraCriteriaName,
            string extraCriteriaValue, bool contains)
        {
            if (!contains)
            {
                return this.BuildEntitiesFromCaml(this.BuildCamlWhereClause(extraCriteriaName,
                    extraCriteriaValue, PreservationNoticeCriteria.Undefined));
            }
            else
            {
                return this.BuildEntitiesFromCaml(this.BuildCamlWhereClauseSearch(extraCriteriaName,
                    extraCriteriaValue, PreservationNoticeCriteria.Undefined));
            }
        }

        public IList<string> GetExtraCriteriaPossibleValues(string extraCriteriaFieldName)
        {
            List<string> possibleValues = new List<string>();
            XmlDocument schema = this.GetCustodiansSchema(extraCriteriaFieldName);
            XmlNodeList choices = schema.SelectNodes("//Field/CHOICES/CHOICE");
            
            if (choices != null && choices.Count > 0)
            {
                // It is a "Choice" column
                foreach (XmlNode choice in choices)
                {
                    possibleValues.Add(choice.InnerText);
                }
            }
            else
            {
                // If no choices found, then must be "single line of text" column
                // Get the distinct list of possible values from the Custodians list
                // First, get the internal filed name of the extra criteria field name passed in
                SPList list = null;
                SPField field = null;
                using (SPSite site = new SPSite(this.CurrentCase.ClientPortalId))
                {
                    using (SPWeb web = site.OpenWeb(this.CurrentCase.Id))
                    {
                        try
                        {
                            field = list.Fields[extraCriteriaFieldName];
                        }
                        catch
                        {
                            try
                            {
                                field = list.Fields.GetFieldByInternalName(extraCriteriaFieldName);
                            }
                            catch { }
                        }
                        if (field != null)
                        {
                            // This is a better query but it does not work!
                            //SPQuery query = new SPQuery();
                            //query.ViewFields = string.Format("<ViewFields><FieldRef Name='{0}'/></ViewFields>", field.InternalName);
                            //DataTable table = this.List.GetItems(query).GetDataTable();
                            DataTable table = list.Items.GetDataTable();
                            string value = string.Empty;
                            foreach (DataRow row in table.Rows)
                            {
                                value = DataHelper.GetString(row[field.InternalName]).Trim();
                                if (!possibleValues.Contains(value))
                                {
                                    possibleValues.Add(value);
                                }
                            }
                        }
                    }
                }
            }
            return possibleValues;
        }

        public string GetDefaultPossibleValue(string extraCriteriaFieldName)
        {
            string defaultCategoryValue = string.Empty;
            XmlDocument schema = this.GetCustodiansSchema(extraCriteriaFieldName);
            XmlNode defaultCategoryValueNode = schema.SelectSingleNode("//Field/Default");
            if (defaultCategoryValueNode != null)
            {
                defaultCategoryValue = defaultCategoryValueNode.InnerText;
            }
            return defaultCategoryValue;
        }

        #endregion

        #region IExtendedPropertiesRepository<Custodian> Members

        public void LoadExtendedProperties(Custodian entity)
        {
            StringBuilder builder = this.GetBaseQueryBuilder();
            builder.Append(this.BuildBaseWhereClause(entity.Key));
            using (IDataReader reader = this.ExecuteQuery(builder.ToString()))
            {
                if (reader != null && reader.Read())
                {
                    this.EntityFactory.LoadExtendedProperties(reader, entity);
                }
            }
        }

        #endregion
    }
}
